package com.ymx.darling.bean.sql;

import com.ymx.darling.exception.ExceptionFactory;
import com.ymx.darling.exception.ExceptionWrapper;
import com.ymx.darling.expression.Expression;
import com.ymx.darling.util.*;

import java.lang.reflect.Array;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * sql解析器
 * @author 爱Java的小于
 */
public class SqlParsingHelper {
    /**sql语句类型的缓存处理*/
    private final static Map<String, SqlCommandType> sqlCommandTypeCache = new ConcurrentHashMap<>();

    /**
     * 解析模版sql 解析#{} 或 ${} 中的数据
     * @param sql sql字符串
     * @param params 占位符参数
     * @return 封装解析后的sql信息
     */
    public static BoundSql parseSqlTemplate(String sql, Object... params) {
        if (params.length == 0) {
            return new BoundSql(sql);
        }
        if (StringUtil.isNull(sql) || containsQuestionMark(sql)) {
            return new BoundSql(sql, ArrayUtil.asList(params));
        } else {
            try {
                return doSqlParseTemplate(sql, params);
            } catch (Exception exception) {
                throw ExceptionFactory.throwStrategy(exception, new ExceptionWrapper(exception, exception.getMessage()));
            }
        }
    }


    /**
     * 解析模版sql及其参数
     * @param sql sql
     * @param param 参数
     * @return SqlStatement
     */
    private static BoundSql doSqlParseTemplate(String sql, Object[] param) throws SQLException {
        // 将多个参数整合到一个集合中 为一个参数时保持不变
        Object paramEntity = formatParameter(param); // 格式化后的参数
        List<String> parameterMappings = new ArrayList<>(); // 参数绑定列表
        StringBuilder sqlBuilder = new StringBuilder(); // 解析后的sql
        char sqlItem;
        for (int index = 0, stringLength = sql.length(); index < stringLength; index++) {
            sqlItem = sql.charAt(index); // 当前字符
            if (checkPlaceholderStart(sql, index)) { // 检查是否出现#{} 或 ${}
                StringBuilder placeholderBuilder = new StringBuilder(); // 构建占位符的名称
                char p;
                for (int pIndex = index + 2; pIndex < stringLength; pIndex++) {
                    p = sql.charAt(pIndex);
                    if (checkPlaceholderStart(sql, pIndex) || (pIndex == stringLength - 1 && p != '}')) {
                        throw new SQLException(String.format("在模板sql中出现占位符%s{未闭合, 错误出现在sql字符索引%d处", sqlItem,index));
                    }
                    if (p == '}') {
                        String placeholderName = placeholderBuilder.toString();
                        if (StringUtil.isNull(placeholderName)) {
                            throw new SQLException(String.format("参数引用错误:在模板sql中出现空的占位符%c{}", sqlItem));
                        }

                        if (sqlItem == '$') { // 将${}替换为参数
                            Object paramValue = getValue(paramEntity, placeholderName);
                            if (paramValue instanceof Collection) {
                                Collection<?> values = (Collection<?>) paramValue;
                                sqlBuilder.append(CollectionUtil.toString(values));
                            } else {
                                sqlBuilder.append(paramValue);
                            }
                        } else { // 将#{}替换为占位符 ? 并将参数放入参数列表
                            sqlBuilder.append('?');
                            parameterMappings.add(placeholderName);
                        }
                        index = pIndex;
                        sqlItem = '\0';
                        break;
                    }
                    placeholderBuilder.append(p);
                }
            }
            if (sqlItem != '\0') sqlBuilder.append(sqlItem);
        }
        return new BoundSql(sqlBuilder.toString(), paramEntity, parameterMappings);
    }


    /**
     * 返回指定实体中的值
     * @param paramEntity 参数实体
     * @param placeholderName 实体中的变量名
     * @return value
     */
    private static Object getValue(Object paramEntity, String placeholderName) {
        Object value = Expression.evalAccess(placeholderName, paramEntity);
        return getValueIfArrayConvertToList(value);
    }


    /**
     * 如果值为数组类型那么将数组转换为List并返回 否则返回原始数据
     * @param value val
     */
    public static Object getValueIfArrayConvertToList(Object value) {
        // 检查是否是数组类型
        if (value != null && value.getClass().isArray()) {
            int length = Array.getLength(value);
            List<Object> elementList = new ArrayList<>(length);
            // 遍历数组并读取每个元素
            for (int i = 0; i < length; i++) {
                elementList.add(Array.get(value, i));
            }
            return elementList;
        }
        return value;
    }


    /**
     * 格式化参数 处理当参数出现多个的情况将多个参数按照参数索引顺序放入Map并返回
     * @param params 参数数组
     * @return 处理后的类型为Map
     */
    public static Object formatParameter(Object... params) {
        if (params.length == 1) {
            return params[0];
        } else {
            Map<String, Object> paramMap = new HashMap<>();
            for(int i = 0; i < params.length; ++i) {
                paramMap.put(String.format("arg%d", i), params[i]);
            }

            return paramMap;
        }
    }


    /**
     * 判断是否以#{ 或者 ${ 开头
     * @param sql sql
     * @param index index
     * @return boolean
     */
    private static boolean checkPlaceholderStart(String sql, int index) {
        char c = sql.charAt(index);
        return (c == '#' || c == '$') && (index + 1 < sql.length()) && (sql.charAt(index + 1) == '{');
    }


    /**
     * 检查字符串中出现字符 ?
     * @param text sql字符串
     * @return boolean
     */
    public static boolean containsQuestionMark(String text) {
        return StringUtil.isNoNull(text) && text.indexOf('?') != -1;
    }


    /**
     * 根据sql字符串返回sql语句的类型
     * @param statementKey 作为解析sql类型后的缓存的key
     * @param sqlStatement sql字符串
     * @return SqlCommandType
     */
    public static SqlCommandType getSqlCommandType(String statementKey, String sqlStatement) {
        if (sqlCommandTypeCache.containsKey(statementKey)) {
            return sqlCommandTypeCache.get(statementKey);
        }

        // 获取sql中第一个字符串的大写形式
        SqlCommandType sqlCommandType;
        String firstSql = StringUtil.extractFirstSubstring(sqlStatement).toUpperCase();
        if ("DELETE".equals(firstSql)) {
            sqlCommandType = SqlCommandType.DELETE;
        } else if ("INSERT".equals(firstSql)) {
            sqlCommandType = SqlCommandType.INSERT;
        } else {
            sqlCommandType = SqlCommandType.UPDATE;
        }

        // 当该sql字符串本身不作为key时放入缓存
        if (!statementKey.equals(sqlStatement)) {
            sqlCommandTypeCache.put(statementKey, sqlCommandType);
        }

        return sqlCommandType;
    }
}
